#include "os_dev.h"
#include "os_sockets.h"
#include "os_shmem.h"
#include "os_queue.h"
#include "loader.h"
#include "os_io.h"

static const device_properties_t dev_props[DEVICE_TYPE_MAX] = 
{ { true, lookup_shmem, open_shmem, close_shmem, init_shmem}, // shared memory => not implemented yet
  { false, lookup_socket, open_socket, close_socket, init_sockets}, // NETWORK_SOCKETS 
  { false, NULL, NULL, NULL, NULL}, // Files => not implemented yet
};

err_t init_devices(void)
{
    uint8_t i = 0;
    for (i = 0; i < DEVICE_TYPE_MAX; i++) {
        if (dev_props[i].init_device) {
	    dev_props[i].init_device();
	}
    }

    return ERR_SUCCESS;
}


err_t cleanup_device(dev_access_t *access_info)
{
    device_t *dev;

    if (!access_info) {
        printf("NULL access info while cleaning up device\n");
	return ERR_INVALID;
    }

    dev = (device_t *)access_info->dev_ptr;
    if (!dev) {
        printf("Null dev found in access info while cleaning up device\n");
	return ERR_INVALID;
    }

    if (dev->device_info.type >= DEVICE_TYPE_MAX || dev_props[dev->device_info.type].close_device == NULL) {
        printf("Invalid type 0x%x or close func not found while cleaning up device\n", dev->device_info.type);
	return ERR_INVALID;
    }

    return dev_props[dev->device_info.type].close_device(access_info);
}


/*
* Post message to owner of a device.
* Current used by EMAC-RX to post received packet
* to the associated socket owner
*/
void post_to_dev_owner(uint32_t data)
{
    dev_post_msg_t *dev_msg = (dev_post_msg_t *)data;
    dev_type_t type;
    err_t err_code;
    device_t *dev = NULL;

    if (!dev_msg) { return; }

    type = dev_msg->dev_info.type;
    if (type >= DEVICE_TYPE_MAX || dev_props[type].lookup_device == NULL) {
        dev_msg->msg.err_code = ERR_NOT_SUPPORTED;
	return;
    }

    err_code = dev_props[type].lookup_device(&dev_msg->dev_info, &dev);
    if (err_code != ERR_SUCCESS) {
        dev_msg->msg.err_code = err_code;
	return;
    }

    if (!dev) {
        dev_msg->msg.err_code = ERR_INTERNAL_ERROR;
        return;
    }

    dev_msg->msg.q_id = dev->owner_qid;
    _enq_queue((uint32_t)&dev_msg->msg);

    return;

}

void close_device(uint32_t data)
{
  dev_handle_t *dev_handle = (dev_handle_t *)data;
  dev_access_t *dev_access = NULL;
  dev_entry_t *dev_entry = NULL;
  err_t dev_error;
  dev_type_t type;

  if (!dev_handle || !dev_handle->pcb_ptr) {
      return;
  }

  dev_access = &dev_handle->access_info;
  type = dev_handle->dev_info.type;

  if (type >= DEVICE_TYPE_MAX || dev_props[type].close_device == NULL) {
      dev_handle->err_code = ERR_INVALID;
      return;
  }
  
  dev_error = cleanup_device(dev_access);
  if (dev_error != ERR_SUCCESS) {
      dev_handle->err_code = dev_error;
      return;
  }

  // Now remove the pcb entry of the device
  dev_entry = (dev_entry_t *)dev_handle->pcb_ptr;
  dev_entry->prev->next = dev_entry->next;
  if (dev_entry->next) {
      dev_entry->next->prev = dev_entry->prev;
  }
  free(dev_entry);

  return;
}



void open_device(uint32_t data)
{
    dev_handle_t *dev_handle = (dev_handle_t *)data;
    dev_type_t type;
    err_t dev_error;
    device_t *dev = NULL;
    dev_entry_t *dev_entry = NULL;
    dev_entry_t *sentinel = NULL;
    pcb_t *cur_pcb = NULL;

    if (!dev_handle) { return; }

    dev_handle->err_code = ERR_SUCCESS;
    type = dev_handle->dev_info.type;
    //printf("Found type:0x%x from ptr:0x%x\n", type, (uint32_t)dev_handle);

    if (type >= DEVICE_TYPE_MAX) {
        dev_handle->err_code = ERR_INVALID;
    } else if (dev_handle->open_mode >= DEVICE_OPEN_MODE_MAX) {
        dev_handle->err_code = ERR_INVALID_MODE;
    }

    if (dev_handle->err_code != ERR_SUCCESS) {
        return;
    }

    if (dev_props[type].open_device == NULL) {
        // device not yet supported
	printf("open_device not found for type: 0x%x.\n", type);
	dev_handle->err_code = ERR_NOT_SUPPORTED;
	return;
    } else {
        dev_error = dev_props[type].open_device(dev_handle, &dev);
	if (dev_error != ERR_SUCCESS) {
	    dev_handle->err_code = dev_error;
	    return;
	}
    }

    if (!dev) {
        dev_handle->err_code = ERR_INTERNAL_ERROR;
	return;
    }

    // Device opened successfully
    dev_handle->dev_info.address = dev->device_info.address;
    dev_handle->dev_info.size = dev->device_info.size;
    dev_handle->access_info.dev_ptr = (void *)dev;

    if (dev_handle->open_mode == DEVICE_MODE_CREATE_ALWAYS ||
        dev_handle->open_mode == DEVICE_MODE_CREATE_IF_NOT_FOUND) {
	dev->owner_qid = dev_handle->qid;
    }
   
    // Create pcb entry for the device
    dev_entry = (dev_entry_t *)calloc(1, sizeof(dev_entry_t));
    if (!dev_entry) {
        dev_props[type].close_device(&dev_handle->access_info);
	dev_handle->err_code = ERR_NO_MEM;
        return;
    }

    dev_entry->access_info = dev_handle->access_info;

    cur_pcb = get_current_process();
    if (cur_pcb) {
        // Invoked from user process

        sentinel = &cur_pcb->device_list;

        sentinel->prev->next = dev_entry;
        dev_entry->next = NULL;
        dev_entry->prev = sentinel->prev;
        sentinel->prev = dev_entry;

        dev_handle->pcb_ptr = (void *)dev_entry;
    }

    return;
}


